package org.qcit.timeplugin;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

public class TimeCountAdviceAdapter extends AdviceAdapter {
    String methodOwner;
    String methodName;
    MethodVisitor mv;
    boolean isAbstractMethod;//是否为抽象方法
    boolean isNativeMethod;//是否为Native方法
    long timeLocalIndex = 0;
    private int lastTimeVarIndex;//其实是一个编号，给后面的load和store用
    private int currentTimeVarIndex;//其实是一个编号，给后面的load和store用
    private int ss;//其实是一个编号，给后面的load和store用
    long threshold;//时间阈值
    protected TimeCountAdviceAdapter(int api,String methodOwner, MethodVisitor methodVisitor,
                                     int access, String name, String descriptor,long threshold) {
        super(api, methodVisitor, access, name, descriptor);
        this.methodOwner = methodOwner;
        this.methodName = name;
        this.mv = methodVisitor;
        this.isAbstractMethod = (access & Opcodes.ACC_ABSTRACT) == Opcodes.ACC_ABSTRACT;
        this.isNativeMethod = (access  & Opcodes.ACC_NATIVE) == Opcodes.ACC_NATIVE;
        this.threshold = threshold;
    }

    @Override
    protected void onMethodEnter() {
        super.onMethodEnter();
        if (!isAbstractMethod && !isNativeMethod){
            System.out.println("====================="+methodOwner+"."+methodName+"正在插入");
            //这里相当于 long currentTime = System.currentTimeMillis();
            lastTimeVarIndex = newLocal(Type.LONG_TYPE);//创建一个局部变量
//            //INVOKESTATIC java/lang/System.currentTimeMillis ()J
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(LSTORE, lastTimeVarIndex);
        }
    }

    @Override
    protected void onMethodExit(int opcode) {
        super.onMethodExit(opcode);
        if (!isAbstractMethod && !isNativeMethod){
            String methodD = methodOwner+"."+methodName+" 耗时：";
////这里相当于 System.out.println(System.currentTimeMillis() - currentTime);
            currentTimeVarIndex = newLocal(Type.LONG_TYPE);
//            //INVOKESTATIC java/lang/System.currentTimeMillis ()J
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(LSTORE, currentTimeVarIndex);
//            //LLOAD 0
            mv.visitVarInsn(Opcodes.LLOAD, currentTimeVarIndex);
            mv.visitVarInsn(Opcodes.LLOAD, lastTimeVarIndex);
  
//            //LSUB
            mv.visitInsn(Opcodes.LSUB);
            ss = newLocal(Type.LONG_TYPE);
            mv.visitVarInsn(LSTORE, ss);
            mv.visitVarInsn(Opcodes.LLOAD, ss);
            mv.visitLdcInsn(new Long(threshold));
            mv.visitInsn(LCMP);
            Label label1 = new Label();
            mv.visitJumpInsn(IFLE, label1);
            mv.visitLdcInsn("timeCount:");
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv.visitLdcInsn(methodD);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitVarInsn(LLOAD, ss);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
            mv.visitLdcInsn("ms");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false);
            mv.visitInsn(POP);
            mv.visitLabel(label1);
            //mv.visitFrame(Opcodes.F_APPEND, 3, new Object[]{Opcodes.LONG, Opcodes.LONG, Opcodes.LONG}, 0, null);
            mv.visitInsn(RETURN);
        }
    }
}
